/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.command.handler;

import java.net.URLDecoder;
import java.util.List;

import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
import com.alibaba.csp.sentinel.datasource.WritableDataSource;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.util.VersionUtil;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;

import static com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry.*;

/**
 * @author jialiang.linjl
 * @author Eric Zhao
 */
@CommandMapping(name = "setRules", desc = "modify the rules, accept param: type={ruleType}&data={ruleJson}")
public class ModifyRulesCommandHandler implements CommandHandler<String> {
    private static final int FASTJSON_MINIMAL_VER = 0x01020C00;

    @Override
    public CommandResponse<String> handle(CommandRequest request) {
        // XXX from 1.7.2, force to fail when fastjson is older than 1.2.12
        // We may need a better solution on this.
        if (VersionUtil.fromVersionString(JSON.VERSION) < FASTJSON_MINIMAL_VER) {
            // fastjson too old
            return CommandResponse.ofFailure(new RuntimeException("The \"fastjson-" + JSON.VERSION
                    + "\" introduced in application is too old, you need fastjson-1.2.12 at least."));
        }
        //这里得到服务端传过来的两个重要参数，这两个参数之前在dashboard的flowControllerV1中已经分析过了
        //type=规则类型（比如流控、降级、授权等）就是表示同步的规则类型
        String type = request.getParam("type");
        // rule data in get parameter
        //data就是具体的规则内容了，是一个json字符串，这个data中的规则内容是包括了
        //本台客户端服务器中指定的规则类型中的所有规则，不仅仅只是新建的规则
        String data = request.getParam("data");
        //非空判断
        if (StringUtil.isNotEmpty(data)) {
            try {
                data = URLDecoder.decode(data, "utf-8");
            } catch (Exception e) {
                RecordLog.info("Decode rule data error", e);
                return CommandResponse.ofFailure(e, "decode rule data error");
            }
        }

        RecordLog.info("Receiving rule change (type: {}): {}", type, data);

        String result = "success";

        //如果是流控规则，则将传过来的流控规则数据保存
        if (FLOW_RULE_TYPE.equalsIgnoreCase(type)) {
            //这里做的是反序列化，在dashboard是将List<FlowRule>进行序列化，这里反序列化
            List<FlowRule> flowRules = JSONArray.parseArray(data, FlowRule.class);
            //这个是核心的方法，就是加载规则到客户端的内存
            //这里我们需要明白的一个事情就是dashboard服务端和客户端端交互的规则都是从内存中取出规则交互的
            //比如服务端dashboard启动过后会到客户端来查询规则，这个时候客户端是通过FlowRuleManager.getRules()
            //返回的规则内容的，而如果要做持久化的话，那么这个持久化的操作也不会是每次查询规则都从持久化的位置去
            //加载，流程还是不变，变的是当客户端保存规则的时候是保存到持久化介质中，然后通过一些手段比如启动一个线程
            //一直监听到介质规则内容的变化，当发生变化了就会马上同步到内存，这样dashboard查询规则的时候就能
            //取到最新的规则，而客户端的规则也会马上生效
            FlowRuleManager.loadRules(flowRules);
            //这个就是持久化的重点了，writeToDataSource这个方法中药接受一个数据源flowDataSource
            //flowDataSource为空则不做任何事情，所以这个就是sentinel持久化提供的一个重要的扩展点
            //也就是说如果我们要持久化规则内容，就需要做一个可写的数据源flowDataSource
            //然后提供一个write方法即可，然后在write方法中编写自己持久化的逻辑即可
            //sentinel默认是没有导入这个数据源的，这个数据源在sentinel-extension中
            //实现类是FileWritableDataSource，sentinel已经写好了，需要你自己去配置
            //有了写的数据源，但是读的数据源呢?读的数据源也是sentinel提供了，还是需要你自己去配置
            //FileRefreshableDataSource是读的数据源，在它里面会开启一个线程一直监控到文件的变化，如果文件发生变化 会马上读取到内存中
            //从而客户端的交易和dashboard就都能取到最新的规则内容数据
            if (!writeToDataSource(getFlowDataSource(), flowRules)) {
                result = WRITE_DS_FAILURE_MSG;
            }
            return CommandResponse.ofSuccess(result);
        } else if (AUTHORITY_RULE_TYPE.equalsIgnoreCase(type)) {
            //这个是授权的规则，处理流程和流控一模一样，只是取到的规则内容不一样
            List<AuthorityRule> rules = JSONArray.parseArray(data, AuthorityRule.class);
            AuthorityRuleManager.loadRules(rules);
            if (!writeToDataSource(getAuthorityDataSource(), rules)) {
                result = WRITE_DS_FAILURE_MSG;
            }
            return CommandResponse.ofSuccess(result);
        } else if (DEGRADE_RULE_TYPE.equalsIgnoreCase(type)) {
            //降级规则
            List<DegradeRule> rules = JSONArray.parseArray(data, DegradeRule.class);
            DegradeRuleManager.loadRules(rules);
            if (!writeToDataSource(getDegradeDataSource(), rules)) {
                result = WRITE_DS_FAILURE_MSG;
            }
            return CommandResponse.ofSuccess(result);
        } else if (SYSTEM_RULE_TYPE.equalsIgnoreCase(type)) {
//            系统规则
            List<SystemRule> rules = JSONArray.parseArray(data, SystemRule.class);
            SystemRuleManager.loadRules(rules);
            if (!writeToDataSource(getSystemSource(), rules)) {
                result = WRITE_DS_FAILURE_MSG;
            }
            return CommandResponse.ofSuccess(result);
        }
        return CommandResponse.ofFailure(new IllegalArgumentException("invalid type"));
    }

    /**
     * Write target value to given data source.
     *
     * @param dataSource writable data source
     * @param value      target value to save
     * @param <T>        value type
     * @return true if write successful or data source is empty; false if error occurs
     */
    private <T> boolean writeToDataSource(WritableDataSource<T> dataSource, T value) {
        //如果传入了可写的数据源，那么调用数据源的write方法持久化，至于你是要持久化到文件、数据库、redis、zk
        if (dataSource != null) {
            try {
                dataSource.write(value);
            } catch (Exception e) {
                RecordLog.warn("Write data source failed", e);
                return false;
            }
        }
        return true;
    }

    private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)";
    private static final String FLOW_RULE_TYPE = "flow";
    private static final String DEGRADE_RULE_TYPE = "degrade";
    private static final String SYSTEM_RULE_TYPE = "system";
    private static final String AUTHORITY_RULE_TYPE = "authority";
}
